#include "Renderer.h"
#include <stdexcept>
#include <fstream>
#include <memory>
#include <stdexcept>
#include <string>
#include <sstream>

#include "VertexShaderBinary.h"
#include "PixelShaderBinary.h"

#include "Utility.h"

namespace
{
	using Utility::Error::CheckDXError;

	com_ptr<ID3D11Device> CreateD3DDevice(unsigned int flags)
	{
		ID3D11Device* device = nullptr;
		ID3D11DeviceContext* context = nullptr;
		D3D_FEATURE_LEVEL level; 

		IDXGIFactory1* fac = nullptr;
		HRESULT hr = CreateDXGIFactory1 (__uuidof(IDXGIFactory1), (void**)(&fac));
		com_ptr<IDXGIFactory1> factory = make_com_ptr (fac);

		IDXGIAdapter1* adap = nullptr;
		hr = factory->EnumAdapters1 (0, &adap);
		com_ptr<IDXGIAdapter1> adapter = make_com_ptr (adap);

		hr = D3D11CreateDevice (adapter,
						D3D_DRIVER_TYPE_UNKNOWN,
						nullptr,
						flags,
						nullptr,
						0,
						D3D11_SDK_VERSION,
						&device,
						&level,
						&context);
		com_ptr<ID3D11Device> device_out = make_com_ptr<ID3D11Device>(device);

		CheckDXError(hr, "D3DCreateDevice");
		return device_out;
	}

	com_ptr<ID3D11Buffer> CreateVertexBuffer(ID3D11Device* device, const D3D11_BUFFER_DESC* desc)
	{
		ID3D11Buffer* buffer;
		HRESULT hr = device->CreateBuffer(desc, nullptr, &buffer);
		com_ptr<ID3D11Buffer> out_buffer = make_com_ptr(buffer);

		CheckDXError(hr, "ID3D11Device::CreateBuffer");
		return out_buffer;
	}

	com_ptr<ID3D11InputLayout> CreateInputLayout(ID3D11Device* device, VertexShaderBinary* shaderbinary)
	{
		D3D11_INPUT_ELEMENT_DESC layout[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, 
				  D3D11_INPUT_PER_VERTEX_DATA, 0 },
			{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, 
			  D3D11_INPUT_PER_VERTEX_DATA, 0 }
		};

		int length = sizeof(layout) / sizeof(layout[0]);
		return shaderbinary->CreateInputLayout(device, layout, length);
	}


}

Renderer::Vertex::Vertex(const vec2f& v, const vec2f& tv)
	: Position(v.x, v.y, 1.337f), TexCoord(tv)
{
}

Renderer::Renderer(const std::string& defaultps, const std::string& defaultvs)
	: m_Device(CreateD3DDevice(DeviceFlags)), m_VertexShaderBinary(defaultvs), m_PixelShaderBinary(defaultps)
{
	ID3D11DeviceContext* context_raw;
	m_Device->GetImmediateContext(&context_raw);
	m_Context = make_com_ptr(context_raw);

	m_DefaultVertexShader = m_VertexShaderBinary.CreateShader(m_Device);
	m_DefaultPixelShader = m_PixelShaderBinary.CreateShader(m_Device);

	D3D11_BUFFER_DESC desc;
	desc.Usage = D3D11_USAGE_DYNAMIC;
	desc.ByteWidth = sizeof(Vertex) * VerticesAmount;
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.MiscFlags = 0;
	desc.StructureByteStride = 0;

	m_VertexBuffer = CreateVertexBuffer(m_Device, &desc);
	m_InputLayout = CreateInputLayout(m_Device, &m_VertexShaderBinary); 
}

com_ptr<ID3D11Device> Renderer::GetDevice() const
{
	return m_Device;
}

com_ptr<ID3D11DeviceContext> Renderer::GetDeviceContext() const
{
	return m_Context;
}

Renderer::SpriteBatch::SpriteBatch(Renderer& renderer)
	: m_Renderer(renderer)
{
	m_Renderer.StartRender();
}

Renderer::SpriteBatch::~SpriteBatch()
{
	m_Renderer.EndRender();
}

void Renderer::PreloadTextureList(const std::list<std::string>& list)
{
	for(auto it = list.begin(); it != list.end(); ++it)
		LoadTexture(*it);
}

void Renderer::LoadTexture(const std::string& path)
{
	m_Cache.AddTexture(path, ::LoadTexture(m_Device, path));
}

void Renderer::StartRender()
{
	D3D11_MAPPED_SUBRESOURCE subresource;
	m_Context->Map(m_VertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &subresource);
	m_VertexBufferPointer = static_cast<Vertex*>(subresource.pData);
	m_NumRendered = 0;
	m_RenderStarted = true;
}

void Renderer::EndRender()
{
	m_Context->Unmap(m_VertexBuffer, 0);
	m_RenderStarted = false;
	Render();
}

void Renderer::RenderTriangle(const vec2f& a, const vec2f& ta,
							   const vec2f& b, const vec2f& tb,
							   const vec2f& c, const vec2f& tc,
							   const std::string& texturepath)
{
	if(!m_RenderStarted)
		StartRender();

	if(m_CurrentTexture != texturepath) // a new texture -> render current batched sprites!
	{
		Render();
		EndRender();
		StartRender();
	}

	ID3D11ShaderResourceView* resource_views[] = {m_Cache.GetTexture(texturepath)};
	m_Context->PSSetShaderResources(0, 1, resource_views);

	Vertex vertizes[] = {Vertex(a, ta), Vertex(b, tb), Vertex(c, tc)};
	memcpy(m_VertexBufferPointer, vertizes, sizeof(Vertex) * 3);
	m_VertexBufferPointer += 3;
	m_NumRendered++;
}

void Renderer::Render()
{
	unsigned int stride = sizeof(Vertex);
	unsigned int offset = 0;
	ID3D11Buffer* vertex_buffers[] = {m_VertexBuffer};
	m_Context->IASetVertexBuffers(0, 1, vertex_buffers, &stride, &offset);

	m_Context->VSSetShader(m_DefaultVertexShader, nullptr, 0U);
	m_Context->PSSetShader(m_DefaultPixelShader, nullptr, 0U);

	m_Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	m_Context->IASetInputLayout(m_InputLayout);

	m_Context->Draw(m_NumRendered * 3, 0);
}